package sql

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"runtime"
	"strings"
	"sync"
	"time"
)

const (
	ENDALL   = "[ENDALL]"
	JUMP_TAG = "[[JUMPT_THIS_ITEM]]"
	PARSE_OK = 1
)

var (
	LastSqlOper *SqlOper
	JUMPT_ITEM  = Item{
		"[[JUMPT_THIS_ITEM]]": {
			"[[JUMPT_THIS_ITEM]]": "[[JUMPT_THIS_ITEM]]",
		},
	}
)

type Dict map[string]interface{}
type Item map[string]Dict
type Table map[string][]Dict

type SqlOper struct {
	InsertLines []string
	CreateLines []string
	OtherLines  []string

	mux          sync.Mutex
	RunDataIn    bool
	State        int
	Path         string
	ENDTAG       string
	FileType     string
	Datas        Table
	ArgsTunnel   chan string
	DataIn       chan Item
	DataIns      chan map[string][]Dict
	Heads        map[string][]string
	Tables       map[string]map[string]string
	FilterTables map[string]bool
	Running      sync.WaitGroup
}

func LoadSql(filePath string) (sqlOper *SqlOper, err error) {
	if LastSqlOper != nil && LastSqlOper.Path == filePath {
		return LastSqlOper, nil
	}
	file, err := os.Open(filePath)
	defer file.Close()

	var allSize int64
	if err != nil {
		return
	}
	sqlOper = new(SqlOper)
	sqlOper.Path = filePath
	sqlOper.ArgsTunnel = make(chan string, 1024)
	sqlOper.Tables = make(map[string]map[string]string)
	sqlOper.Datas = make(Table)
	sqlOper.DataIn = make(chan Item, 1024)
	sqlOper.DataIns = make(chan map[string][]Dict, 24)
	sqlOper.Heads = make(map[string][]string)
	sqlOper.FilterTables = make(map[string]bool)
	sqlOper.FileType = "mysql"
	ENDTAG := ";"
	sqlOper.ENDTAG = ENDTAG

	buf := bufio.NewReader(file)

	if ft, err := os.Stat(filePath); err == nil {
		allSize = ft.Size()
	}
	oneSqlOper := ""
	num := 0
	// var nowSize int64
	bar := NewBar(allSize)
	IsInCommnet := false
	go func() {
		for {
			one := <-sqlOper.ArgsTunnel
			if one == ENDALL {
				break
			}
			sqlOper.ParseCreate(one)
		}
	}()
	go sqlOper.DataInDb(func(ti time.Time) {
		bar.Log("left Runtime:", runtime.NumGoroutine(), " | at time:", ti)
	})

	for {
		num++
		lineBytes, _, lerr := buf.ReadLine()

		if num%100000 == 0 {
			bar.Log("Read line: ", num)
		}
		if lerr != nil {
			break
		}

		bar.Increment(int64(len(lineBytes)))
		line := string(bytes.TrimSpace(lineBytes))
		if line == "" {
			continue
		}
		if strings.HasPrefix(line, "--") {
			continue
		}
		if strings.HasPrefix(line, "/*") {
			IsInCommnet = true
			if strings.Contains(line, "*/") {
				IsInCommnet = false
			}
			continue
		}

		if IsInCommnet {
			if strings.Contains(line, "SQL Server") && num < 200 {
				sqlOper.FileType = "mssql"
				sqlOper.ENDTAG = "GO"
				fmt.Println("[Detect sql file is SQL Server]")
			}
			if strings.Contains(line, "*/") {
				IsInCommnet = false
			}
			continue
		}
		switch sqlOper.FileType {
		case "mssql":
			if line == sqlOper.ENDTAG && sqlOper.FileType == "mssql" {
				if strings.HasPrefix(oneSqlOper, "CREATE") {
					if strings.HasPrefix(oneSqlOper, "CREATE TABLE") {
						sqlOper.ArgsTunnel <- oneSqlOper
					}
					sqlOper.CreateLines = append(sqlOper.CreateLines, oneSqlOper)

				} else if strings.HasPrefix(oneSqlOper, "INSERT INTO") {
					// if len(sqlOper.ArgsTunnel) < 712 {
					// 	sqlOper.ArgsTunnel <- oneSqlOper
					// } else {
					sqlOper.InsertLines = append(sqlOper.InsertLines, oneSqlOper)
					// }

				}
				// else {
				// 	sqlOper.OtherLines = append(sqlOper.OtherLines, oneSqlOper)
				// }

				oneSqlOper = ""
			} else {
				oneSqlOper += line + "\n"
			}
		default:

			if strings.HasSuffix(line, ";") {
				oneSqlOper += line + "\n"
				if strings.HasPrefix(oneSqlOper, "CREATE") {
					if strings.HasPrefix(oneSqlOper, "CREATE TABLE") {
						sqlOper.ArgsTunnel <- oneSqlOper
					}
					sqlOper.CreateLines = append(sqlOper.CreateLines, oneSqlOper)

				} else if strings.HasPrefix(oneSqlOper, "INSERT INTO") {
					// if len(sqlOper.ArgsTunnel) < 712 {
					// 	sqlOper.ArgsTunnel <- oneSqlOper
					// } else {
					sqlOper.InsertLines = append(sqlOper.InsertLines, oneSqlOper)
					// }

				}
				//  else {
				// 	sqlOper.OtherLines = append(sqlOper.OtherLines, oneSqlOper)
				// }

				oneSqlOper = ""
			} else {
				oneSqlOper += line + "\n"
			}
		}

	}
	sqlOper.ArgsTunnel <- ENDALL
	for {
		if len(sqlOper.ArgsTunnel) == 0 {
			break
		}
	}
	LastSqlOper = sqlOper
	return
}

func (sqlOper *SqlOper) ParseWith(one string, after func(t time.Duration)) {
	// sqlOper.RunTunnel <- 1
	// defer func() { <-sqlOper.RunTunnel }()
	if strings.HasPrefix(one, "CREATE TABLE") {
		sqlOper.ParseCreate(one)
	} else if strings.HasPrefix(one, "INSERT INTO") {
		tableName, head, bodys := sqlOper.InsertFields(one)
		if sqlOper.FilterTable(tableName) {
			switch head {
			case "":
				// Debug("header")
				sqlOper.ParserInsertNoHead(tableName, bodys, after)
			default:
				sqlOper.ParserInsertHasHead(tableName, head, bodys, after)
			}
		} else {
			sqlOper.DataIn <- JUMPT_ITEM
		}

	}
}

func (sqlOper *SqlOper) Add(item Item) {
	sqlOper.mux.Lock()
	defer sqlOper.mux.Unlock()
	defer sqlOper.Running.Done()
	for k, v := range item {
		if k == JUMP_TAG {
			break
		}
		sqlOper.Datas[k] = append(sqlOper.Datas[k], v)
	}
}

func (sqlOper *SqlOper) Adds(items map[string][]Dict) {
	sqlOper.mux.Lock()
	defer sqlOper.mux.Unlock()
	defer sqlOper.Running.Done()
	for k, vs := range items {
		for _, v := range vs {
			sqlOper.Datas[k] = append(sqlOper.Datas[k], v)
		}
	}
}

func (sqlOper *SqlOper) DataInDb(every func(ti time.Time)) {
	// count := 0
	sqlOper.RunDataIn = true
	// C := time.NewTicker(5 * time.Second)
	defer func() {
		sqlOper.RunDataIn = false
	}()
	for {

		select {
		case item := <-sqlOper.DataIn:
			if item != nil {
				sqlOper.Add(item)
			} else {
				break
			}
		case items := <-sqlOper.DataIns:
			if len(items) > 0 {
				sqlOper.Adds(items)
			}

		}

	}
}

func (sqlOper *SqlOper) ParseTableName(name string) string {
	return string(EscapeTableRe.ReplaceAll([]byte(name), []byte("")))
}

func (sqlOper *SqlOper) ParserLeft() {
	if len(sqlOper.InsertLines) == 0 {
		return
	}

	var line string
	var lineMaxLen int
	var lc int
	allCount := int64(len(sqlOper.InsertLines))
	bar := NewBar(allCount)
	oneShow := allCount / 1000
	args := make(chan string, 1024)
	// All := 0
	go func() {

		for {
			line := <-args
			if line == ENDALL {
				// bar.Println("[正在缓存处理结束 退出循环]")
				// if len(sqlOper.Running) > 0 {
				// sqlOper.Running.Done()
				// }
				break
			}
			go sqlOper.ParseWith(line, func(usedTIme time.Duration) {
				bar.Increment(1)
				// All--
				if oneShow != 0 {
					if bar.NowSize%oneShow == 0 || len(sqlOper.InsertLines) == 0 {
						bar.Log("解析完:", bar.NowSize, "/剩余:", len(sqlOper.InsertLines),
							" 最长单行: ", lineMaxLen,
							" 这行长: ", lc,
							" 用时: ", usedTIme,
							// " 处理剩余: ", All,
						)
					}
				} else {
					bar.Log("解析完:", bar.NowSize, "/剩余:", len(sqlOper.InsertLines),
						" 最长单行: ", lineMaxLen,
						" 这行长: ", lc,
						" 用时: ", usedTIme,
						// " 处理剩余: ", All,
					)
				}
			})

		}
	}()
	// tick := time.NewTicker(7 * time.Second)
	for {
		line = sqlOper.InsertLines[0]
		sqlOper.InsertLines[0] = ""
		sqlOper.InsertLines = sqlOper.InsertLines[1:]

		sqlOper.Running.Add(1)
		// All++
		lc = len(line)
		// if lc > 10000 {
		// 	log.Fatal("One line overflow to 10000 length:", line)
		// }
		if lc > lineMaxLen {
			lineMaxLen = lc
		}

		args <- line
		// sleepCount := 0
		// for {
		// 	if len(args) < 1024 {
		// 		args <- line
		// 		break
		// 	} else {
		// 		time.Sleep(3 * time.Millisecond)
		// 		if sleepCount > 1000 {
		// 			log.Fatal(line, "sleep long so exit....")
		// 		} else {
		// 			sleepCount++
		// 		}
		// 	}
		// }

		if len(sqlOper.InsertLines) == 0 {
			// bar.Println("[正在处理结束 退出循环]")
			break
		} else {
			// select {
			// case t := <-tick.C:
			// 	bar.Log("Insert line:", len(sqlOper.InsertLines), " at:", t)
			// }
		}
	}
	args <- ENDALL
	sqlOper.Running.Wait()
	bar.Println("[处理结束]")
	// for {
	// 	if len(args) == 0 {
	// 		if !mssql.RunDataIn {
	// 			break
	// 		}
	// 	}
	// }
	sqlOper.State = PARSE_OK
	return
}

func (sqlOper *SqlOper) Ls() []string {
	datas := []string{}
	for k := range sqlOper.Datas {
		datas = append(datas, k)
	}
	return datas
}

func (sqlOper *SqlOper) GetKeys(tableName string) []string {
	datas := []string{}
	if ts, ok := sqlOper.Datas[tableName]; ok {
		if len(ts) > 0 {
			for k := range ts[0] {
				datas = append(datas, k)
			}
		}
	}
	return datas
}

func (sqlOper *SqlOper) Query(name string, g G) (out []Dict) {
	if ts, ok := sqlOper.Datas[name]; ok {

		for _, item := range ts {
			found := true
			for k, v := range g {
				if fmt.Sprint(item[k]) != v {
					found = false
					break
				}
			}
			if found {
				out = append(out, item)
			}
		}

	}
	return
}

func (sqlOper *SqlOper) Search(name, key string) (out []Dict) {
	if ts, ok := sqlOper.Datas[name]; ok {

		for _, item := range ts {
			found := false
			for _, v := range item {
				if strings.Contains(fmt.Sprint(v), key) {
					found = true
					break
				}
			}
			if found {
				out = append(out, item)
			}
		}

	}
	return
}
